﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;

using ESRI.ArcGIS.Client;
using ESRI.ArcGIS.Client.Geometry;
using ESRI.ArcGIS.Client.Symbols;
using ESRI.ArcGIS.Client.Tasks;
using ESRI.ArcGIS.Client.Toolkit;

namespace GISforTT_API
{
    public sealed class MapLayersManager
    {
        string tiledServiceLayerIDBase = "OnlineLayer";

        //public delegate void MapLayerAddedDelegate(Layer aMapLayer);
        public delegate void MapLayerAddedDelegate(String aMapLayerID);
        public delegate void MapLayerDeletedDelegate(Layer aMapLayer);
        public delegate void MapLayersInitializedDelegate();
        public delegate void AnnotationLayersUpdatedDelegate();

        public event MapLayerAddedDelegate MapLayerAdded;
        public event MapLayerDeletedDelegate MapLayerDeleted;
        public event MapLayersInitializedDelegate MapLayersInitialized;
        public event AnnotationLayersUpdatedDelegate AnnotationLayersUpdated;

        //timer to synchronize updates across layers.
        private DispatcherTimer UpdateTimer;


        #region Singleton stuff

        /// <summary>
        /// Returns the instance of the MapLayersManager singleton class
        /// </summary>
        public static MapLayersManager Instance { get; private set; }

        static MapLayersManager() { Instance = new MapLayersManager(); }

        #endregion

        private List<MapLayerDef> defaultLayerTypes;
        
        public bool bookmarkLayerAdded = false;
        public bool annotationsLayerAdded = false;
        public bool onlineAnnotationLayersAdded = false;
        public bool customGraphicsLayerAdded = false;

        /* Keep list of map layers */
        private LayerCollection layers;
        public LayerCollection MapLayers
        {
            get
            {
                return layers;
            }
        }

        public LayerCollection CloneLayers
        {
            get
            {
                LayerCollection outLayers = new LayerCollection();
                foreach (Layer layer in layers)
                {
                    Layer clone = CloneLayer(layer);
                    if (clone != null)
                        outLayers.Add(clone);
                }
                return outLayers;
            }
        }

        MapLayersManager()
        {
            layers = new LayerCollection();
            defaultLayerTypes = new List<MapLayerDef>();

        }

/*        private GraphicsLayer _annotationsLayer;
        public GraphicsLayer annotationsLayer { get { return _annotationsLayer; } }
        */
        private FeatureLayer _pointAnnotationLayer;
        private FeatureLayer _lineAnnotationLayer;
        private FeatureLayer _polygonAnnotationLayer;

        public FeatureLayer pointAnnotationLayer { get { return _pointAnnotationLayer; } }
        public FeatureLayer lineAnnotationLayer { get { return _lineAnnotationLayer; } }
        public FeatureLayer polygonAnnotationLayer { get { return _polygonAnnotationLayer; } }


        private void initializeDefaultLayerTypes()
        {
            if (defaultLayerTypes == null)
            {
                defaultLayerTypes = new List<MapLayerDef>();
            }

            defaultLayerTypes.Add(new MapLayerDef(MapLayerDef.MapLayerType.SatelliteImagery));
            defaultLayerTypes.Add(new MapLayerDef(MapLayerDef.MapLayerType.ShadedRelief));
            defaultLayerTypes.Add(new MapLayerDef(MapLayerDef.MapLayerType.StreetMap));
            defaultLayerTypes.Add(new MapLayerDef(MapLayerDef.MapLayerType.OfflineAnnotationLayer));
        }


        /// <summary>
        /// Setting the default layer Types
        /// </summary>
        /// <param name="layerTypes">List of Map Layer Types</param>        
        public void setDefaultLayerTypes(List<MapLayerDef> layerTypes)
        {
            defaultLayerTypes = layerTypes;            
        }

        /// <summary>
        /// Reset the map layers to the default set
        /// </summary>
        public void ResetDefaultLayers()
        {
            layers.Clear();
            InitializeDefaultMapLayers();
        }

        /// <summary>
        /// Initialize the default list of map layers
        /// </summary>
        public void InitializeDefaultMapLayers()
        {
            if (defaultLayerTypes == null || defaultLayerTypes.Count() == 0)
            {
                initializeDefaultLayerTypes();
            }

            ArcGISTiledMapServiceLayer tiledServiceLayer;
                        
            // loop on the default layer types and create them
            foreach (MapLayerDef layerType in defaultLayerTypes)
            {
                switch (layerType.LayerType)
                {
                    case MapLayerDef.MapLayerType.StreetMap:
                        tiledServiceLayer = new ArcGISTiledMapServiceLayer();
                        tiledServiceLayer.ID = layerType.LayerID;;                        
                        tiledServiceLayer.Opacity = 1;
                        //tiledServiceLayer.Visible = false;
                        tiledServiceLayer.Url = layerType.LayerURL;
                        AddLayer(tiledServiceLayer);
                        break;

                    case MapLayerDef.MapLayerType.SatelliteImagery:
                        tiledServiceLayer = new ArcGISTiledMapServiceLayer();
                        tiledServiceLayer.ID = layerType.LayerID;
                        tiledServiceLayer.Opacity = 1;
                        tiledServiceLayer.Visible = true;
                        tiledServiceLayer.Url = layerType.LayerURL;
                        AddLayer(tiledServiceLayer);
                        break;

                    case MapLayerDef.MapLayerType.ShadedRelief:
                        tiledServiceLayer = new ArcGISTiledMapServiceLayer();
                        tiledServiceLayer.ID = layerType.LayerID;
                        tiledServiceLayer.Opacity = 1;
                        tiledServiceLayer.Visible = true;                        
                        tiledServiceLayer.Url = layerType.LayerURL;
                        AddLayer(tiledServiceLayer);
                        break;

                    case MapLayerDef.MapLayerType.OnlinePublishedMap:
                        tiledServiceLayer = new ArcGISTiledMapServiceLayer();
                        tiledServiceLayer.Visible = true;
                        tiledServiceLayer.Opacity = 1;
                        tiledServiceLayer.ID = layerType.LayerID;
                        tiledServiceLayer.Url = layerType.LayerURL;

                        AddLayer(tiledServiceLayer);
                        break;

                    case MapLayerDef.MapLayerType.OfflineAnnotationLayer:
                        GraphicsLayer _annotationsLayer = new GraphicsLayer();
                        _annotationsLayer.Visible = true;
                        _annotationsLayer.Opacity = 1;
                        _annotationsLayer.ID = layerType.LayerID;
                        
                        AddLayer(_annotationsLayer);
                        annotationsLayerAdded = true;
                        break;

                    case MapLayerDef.MapLayerType.OnlinePointAnnotationsLayer:
                        _pointAnnotationLayer = new FeatureLayer();
                        _pointAnnotationLayer.Visible = true;
                        _pointAnnotationLayer.Opacity = 1;
                        _pointAnnotationLayer.ID = layerType.LayerID;
                        _pointAnnotationLayer.Url = layerType.LayerURL;
                        _pointAnnotationLayer.AutoSave = true;
                        
                        AddLayer(_pointAnnotationLayer);
                        onlineAnnotationLayersAdded = true;

                        break;

                    case MapLayerDef.MapLayerType.OnlineLineAnnotationsLayer:
                        
                        _lineAnnotationLayer = new FeatureLayer();
                        _lineAnnotationLayer.Visible = true;
                        _lineAnnotationLayer.Opacity = 1;
                        _lineAnnotationLayer.ID = layerType.LayerID;
                        _lineAnnotationLayer.Url = layerType.LayerURL;
                        _lineAnnotationLayer.AutoSave = true;

                        AddLayer(_lineAnnotationLayer);
                        onlineAnnotationLayersAdded = true;
                        break;

                    case MapLayerDef.MapLayerType.OnlinePolygonAnnotationsLayer:
                        _polygonAnnotationLayer = new FeatureLayer();
                        _polygonAnnotationLayer.Visible = true;
                        _polygonAnnotationLayer.Opacity = 1;
                        _polygonAnnotationLayer.ID = layerType.LayerID;
                        _polygonAnnotationLayer.Url = layerType.LayerURL;
                        
                        AddLayer(_polygonAnnotationLayer);
                        onlineAnnotationLayersAdded = true;

                        break;
                }
            }

            if (onlineAnnotationLayersAdded)
            {
                //Create a timer that will request a server update in three seconds, to allow time to store new features remotely.
                UpdateTimer = new DispatcherTimer();
                UpdateTimer.IsEnabled = false;
                UpdateTimer.Interval = new TimeSpan(0, 0, 3);
                UpdateTimer.Tick += UpdateAnnotationLayersTimer;
            }

            GraphicsLayer graphicsLayer = new GraphicsLayer();
            graphicsLayer.ID = "BookmarksLayer";
            AddLayer(graphicsLayer);
            bookmarkLayerAdded = true;

            if (MapLayersInitialized != null)
                MapLayersInitialized();

        }

        /// <summary>
        /// Returns a list of custom online published layers
        /// </summary>
        /// <returns></returns>
        public LayerCollection getCustomOnlinePublishedLayers()
        {
            LayerCollection customLayers = new LayerCollection();

            foreach (Layer layer in layers)
            {
                string id = layer.ID;
                string sub = id.Substring(0, tiledServiceLayerIDBase.Length);
                if (sub.Equals(tiledServiceLayerIDBase))
                {
                    customLayers.Add(layer);
                }
            }

            return customLayers;
        }

        public void SetLayerVisible(String layerId)
        {
            foreach (Layer layer in layers)
            {
                if (layer.ID == layerId)
                {
                    layer.Visible = true;
                }
            }
        }

        public void SetLayerNotVisible(String layerId)
        {
            foreach (Layer layer in layers)
            {
                if (layer.ID == layerId)
                {
                    layer.Visible = false;
                }
            }
        }


        #region Map Layers Management

        /// <summary>
        /// Add a layer to the set of layers
        /// </summary>
        /// <param name="newLayer">New layer to be added</param>
        public void AddLayer(Layer newLayer)
        {
            layers.Add(newLayer);

            if (MapLayerAdded != null)
                MapLayerAdded(newLayer.ID);
        }

        /// <summary>
        /// Add a new layer
        /// </summary>
        /// <param name="id">Layer ID</param>
        /// <param name="url">URL of the layer</param>
        /// <returns></returns>
        public Layer AddLayer(string id, string url)
        {
            ArcGISTiledMapServiceLayer newLayer = new ArcGISTiledMapServiceLayer();
            newLayer.Url = url;
            newLayer.Visible = true;
            newLayer.Opacity = 1;
            newLayer.ID = id;

            layers.Add(newLayer);
            if (MapLayerAdded != null)
                MapLayerAdded(newLayer.ID);

            return newLayer;
        }

        /// <summary>
        /// Remove a layer
        /// </summary>
        /// <param name="id">ID of the layer to be removed</param>
        /// <returns></returns>
        public bool RemoveLayer(string id)
        {
            foreach (Layer layer in layers)
            {
                if (layer.ID.Equals(id))
                {
                    layers.Remove(layer);

                    if (MapLayerDeleted != null)
                        MapLayerDeleted(layer);

                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Create a clone of a layer
        /// </summary>
        /// <param name="originalLayer">Layer to be cloned</param>
        /// <returns></returns>
        /*public Layer CloneLayer(Layer originalLayer)
        {
            switch (originalLayer.GetType().Name)
            {
                case "ArcGISTiledMapServiceLayer":
                    ArcGISTiledMapServiceLayer cloneLayer = new ArcGISTiledMapServiceLayer();
                    cloneLayer.Url = (originalLayer as ArcGISTiledMapServiceLayer).Url;
                    cloneLayer.ID = (originalLayer as ArcGISTiledMapServiceLayer).ID;
                    cloneLayer.Opacity = (originalLayer as ArcGISTiledMapServiceLayer).Opacity;
                    return cloneLayer;
                case "ArcGISDynamicMapServiceLayer":
                    ArcGISDynamicMapServiceLayer cloneLayer2 = new ArcGISDynamicMapServiceLayer();
                    cloneLayer2.Url = (originalLayer as ArcGISDynamicMapServiceLayer).Url;
                    cloneLayer2.ID = (originalLayer as ArcGISDynamicMapServiceLayer).ID;
                    cloneLayer2.Opacity = (originalLayer as ArcGISDynamicMapServiceLayer).Opacity;
                    return cloneLayer2;
                case "GraphicsLayer":
                    GraphicsLayer cloneLayer3 = new GraphicsLayer();
                    cloneLayer3.ID = (originalLayer as GraphicsLayer).ID;
                    cloneLayer3.Opacity = (originalLayer as GraphicsLayer).Opacity;
                    return cloneLayer3;
                default:
                    return null;

            }
        }*/

        public Layer CloneLayer(Layer originalLayer)
        {

            if (typeof(ClonableLayer).IsAssignableFrom(originalLayer.GetType()))
            {
                // If the layer implements its own clone function, use this.
                return ((ClonableLayer)originalLayer).CloneLayer();
            }
            else if (originalLayer is ArcGISTiledMapServiceLayer)
            {
                ArcGISTiledMapServiceLayer cloneLayer = new ArcGISTiledMapServiceLayer();
                cloneLayer.Url = (originalLayer as ArcGISTiledMapServiceLayer).Url;
                cloneLayer.ID = (originalLayer as ArcGISTiledMapServiceLayer).ID;
                cloneLayer.Opacity = (originalLayer as ArcGISTiledMapServiceLayer).Opacity;
                cloneLayer.Visible = originalLayer.Visible;
                return cloneLayer;
            }
            else if (originalLayer is ArcGISDynamicMapServiceLayer)
            {
                ArcGISDynamicMapServiceLayer cloneLayer = new ArcGISDynamicMapServiceLayer();
                cloneLayer.Url = (originalLayer as ArcGISDynamicMapServiceLayer).Url;
                cloneLayer.ID = (originalLayer as ArcGISDynamicMapServiceLayer).ID;
                cloneLayer.Opacity = (originalLayer as ArcGISDynamicMapServiceLayer).Opacity;
                cloneLayer.Visible = originalLayer.Visible;
                return cloneLayer;
            }
            else if (originalLayer is FeatureLayer)
            {
                FeatureLayer cloneLayer = new FeatureLayer();
                cloneLayer.Url = (originalLayer as FeatureLayer).Url;
                cloneLayer.ID = (originalLayer as FeatureLayer).ID;
                cloneLayer.Opacity = (originalLayer as FeatureLayer).Opacity;
                cloneLayer.Visible = originalLayer.Visible;
                return cloneLayer;
            }
            else if (originalLayer is GraphicsLayer)
            {
                GraphicsLayer cloneLayer = new GraphicsLayer();
                cloneLayer.ID = (originalLayer as GraphicsLayer).ID;
                cloneLayer.Opacity = (originalLayer as GraphicsLayer).Opacity;
                cloneLayer.Visible = originalLayer.Visible;
                DeepCopyGraphics((originalLayer as GraphicsLayer), cloneLayer);
                return cloneLayer;
            }
            else
                return null;
        }


        public Layer Clonelayer(String layerID)
        {
            foreach (Layer layer in layers)
            {
                if (layer.ID == layerID)
                {
                    return CloneLayer(layer);
                }
            }
            return null;
        }


        // Makes a deep copy of each Graphic in the originalLayer, and adds it to the supplied cloneLayer.
        public void DeepCopyGraphics(GraphicsLayer originalLayer, GraphicsLayer cloneLayer)
        {
            foreach (Graphic g in originalLayer.Graphics)
            {
                Graphic newGraphic = new Graphic();
                newGraphic.Symbol = g.Symbol;
                newGraphic.Geometry = Geometry.Clone(g.Geometry);
                cloneLayer.Graphics.Add(newGraphic);
            }
        }

        public void UpdateAnnotationLayers()
        {
            UpdateTimer.IsEnabled = true;
        }

        private void UpdateAnnotationLayersTimer(object sender, EventArgs e)
        {
            UpdateTimer.IsEnabled = false;
            if (AnnotationLayersUpdated != null)
                AnnotationLayersUpdated();
        }

        #endregion
    }

}
